#define _POSIX_SOURCE
#define _BSD_SOURCE

#include "dominion.h"
#include "dominion_helpers.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include "rngs.h"

#ifndef MAX_PLAYER
#define MAX_PLAYER 4
#endif

#ifndef SAMPLE_SIZE
#define SAMPLE_SIZE 100
#endif

int checkAdventurer(struct gameState *post, int handPos, int pos1, int pos2, int casetype, int shuffleFlag, int totalCoinCount, int verboseFlag)
{
	struct gameState pre;
	int i, remaining, coinCount = 0, postTotalCardCount;

	if (casetype != 2) {
		assert (pos1 != pos2);
	}

	// Save initial conditions
	int preDeckCount = post->deckCount[post->whoseTurn];
	int preDiscardCount = post->discardCount[post->whoseTurn];
	int preHandCount = post->handCount[post->whoseTurn];
	int prePlayedCount = post->playedCardCount;
	int preTotalCardCount = preDeckCount + preDiscardCount + preHandCount + prePlayedCount;

	/* Copy memory located at post to memory located at pre */
	memcpy(&pre, post, sizeof(struct gameState));

	cardEffect(adventurer, -1, -1, -1, post, handPos, NULL);

	if (shuffleFlag == 0) {
		// Toss all cards that are not treasure cards
		if (pos2 > pos1) {
			remaining = pos1;
		} else {
			remaining = pos2;
		}

		// Save final conditions
		int postDiscardCount = pre.discardCount[pre.whoseTurn] + preDeckCount - remaining - 2;
		pre.deckCount[pre.whoseTurn] = remaining;
		pre.discardCount[pre.whoseTurn] = postDiscardCount;

		for (i = preDeckCount - 1; i >= remaining; i--) {
			if (i != pos1 && i != pos2) {
				pre.discard[pre.whoseTurn][(postDiscardCount--) - 1] = pre.deck[pre.whoseTurn][i];
			} else {
				pre.hand[pre.whoseTurn][pre.handCount[pre.whoseTurn]++] = pre.deck[pre.whoseTurn][i];
			}
		}

		// Add adventurer to played cards - removed because I know this fails
		//discardCard(handPos, pre.whoseTurn, &pre, 0);

		assert(memcmp(&pre, post, sizeof(struct gameState)) == 0);
	} else {

		/* Since I cannot find a reasonable way to redo a shuffle so that the pre and post are the same,
		 * I will instead test for the counts and the coin total.
		 */

		/* Check that hand gained 2 cards and lost 1 card */
		//assert(post->handCount[post->whoseTurn] = preHandCount + 1);
		 assert(post->handCount[post->whoseTurn] = preHandCount + 2);

		/* Check that 2 treasure cards have been added to the hand */
		for (i = 0; i < post->handCount[post->whoseTurn]; i++) {
			if (post->hand[post->whoseTurn][i] == copper || post->hand[post->whoseTurn][i] == silver || post->hand[post->whoseTurn][i] == gold) {
				coinCount++;
			}
			//printf("Current card %d; coin total: %d\n", post->hand[post->whoseTurn][i], coinCount);
		}
		if (verboseFlag) {
			printf("Coins Expected: %3d, Coins Actual: %3d\n", totalCoinCount, coinCount);
		}
		assert(coinCount == totalCoinCount);
		
		/* Check that the total card count is unchanged */
		postTotalCardCount = post->deckCount[post->whoseTurn] + post->discardCount[post->whoseTurn] + post->handCount[post->whoseTurn] + post->playedCardCount;
		assert(preTotalCardCount == postTotalCardCount);

		/* Check that adventurer is tossed from the hand to the playedCards*/
		//assert(post->playedCards[post->playedCardCount - 1] == adventurer);
	}

	if (verboseFlag && casetype == 1) {
		printf("Pre : dck=%3d hnd=%d dsc=%3d\n", pre.deckCount[pre.whoseTurn], pre.handCount[pre.whoseTurn], pre.discardCount[pre.whoseTurn]);
		printf("Post: dck=%3d hnd=%d dsc=%3d\n", post->deckCount[post->whoseTurn], post->handCount[post->whoseTurn], post->discardCount[post->whoseTurn]);
	}

	return 0;
}

void sigabrt_catch(int sig)
{
	printf("SIGABRT has been caught\n");
	exit(0);
}

/* This will test the case where no shuffle takes place: pos1 and pos2 refer
 * to positions in the deck.
 */ 
int noShuffleCase(int verboseFlag)
{
	int i, n, pos1, pos2, handPos;
	struct gameState G;

	printf("START RANDOM TEST CASE: NO SHUFFLE.\n");

	for (n = 1; n <= 20000; n++) {
		for (i = 0; i < sizeof(struct gameState); i++) {
			((char *) &G)[i] = floor(Random() * 256);
		}

		/* Randomly Set Game Numbers */
		G.numPlayers = floor(Random() * (MAX_PLAYER - 1)) + 2;
		G.whoseTurn = floor(Random() * G.numPlayers);
		G.deckCount[G.whoseTurn] = floor(Random() * SAMPLE_SIZE);
		G.discardCount[G.whoseTurn] = floor(Random() * SAMPLE_SIZE);
		G.handCount[G.whoseTurn] = floor(Random() * SAMPLE_SIZE) + 1;
		G.playedCardCount = floor(Random() * SAMPLE_SIZE);
		G.coins = 0;

		/* For purposes of testing, set deck up where shuffle isn't required */
		while (G.deckCount[G.whoseTurn] < 2) {
			G.deckCount[G.whoseTurn] = floor(Random() * SAMPLE_SIZE);
		}

		/* Make 2 cards in deck be coins */
		pos1 = floor(Random() * G.deckCount[G.whoseTurn]);
		G.deck[G.whoseTurn][pos1] = floor(Random() * (gold - copper + 1)) + copper;
		pos2 = floor(Random() * G.deckCount[G.whoseTurn]);
		while (pos2 == pos1) {
			pos2 = floor(Random() * G.deckCount[G.whoseTurn]);
		}
		G.deck[G.whoseTurn][pos2] = floor(Random() * (gold - copper + 1)) + copper;

		/* Set 1 random card in hand as adventurer */
		handPos = floor(Random() * G.handCount[G.whoseTurn]);

		/* Fill rest of the deck with cards */
		for (i = 0; i < G.deckCount[G.whoseTurn]; i++) {
			if (i != pos1 && i != pos2) {
				G.deck[G.whoseTurn][i] = floor(Random() * treasure_map);
				while (G.deck[G.whoseTurn][i] == copper || G.deck[G.whoseTurn][i] == silver || G.deck[G.whoseTurn][i] == gold) {
					G.deck[G.whoseTurn][i] = floor(Random() * treasure_map);
				}
			}
		}

		/* Fill discard with cards */
		for (i = 0; i < G.discardCount[G.whoseTurn]; i++) {
			G.discard[G.whoseTurn][i] = floor(Random() * treasure_map);
		}

		/* Fill hand with cards */
		for (i = 0; i < G.handCount[G.whoseTurn]; i++) {
			
			if (i == handPos) {
				G.hand[G.whoseTurn][i] = adventurer;
			} else {	
				G.hand[G.whoseTurn][i] = floor(Random() * treasure_map);
			}

			/* Set up coins based on cards in hand */
			if (G.hand[G.whoseTurn][i] == copper) {
				G.coins += 1;
			} else if (G.hand[G.whoseTurn][i] == silver) {
				G.coins += 2;
			} else if (G.hand[G.whoseTurn][i] == gold) {
				G.coins += 3;
			}
		}

		if (verboseFlag) {
			printf("Test #%5d: plyr=%d dck=%3d hnd=%3d dsc=%3d hndPos=%3d pos1=%3d pos2=%3d\n", 
					n, G.whoseTurn, G.deckCount[G.whoseTurn], G.handCount[G.whoseTurn], G.discardCount[G.whoseTurn], handPos, pos1, pos2);
		}
		
		checkAdventurer(&G, handPos, pos1, pos2, 1, 0, -1, verboseFlag);
	}
	return 0;
}

/* This will test the case where a shuffle takes place: deckPos refers
 * to the position of the treasure card in the hand; discardPos refers to
 * the position of the treasure card in the discard.
 */ 
int oneShuffleCase(int verboseFlag)
{
	int i, n, deckPos, discardPos, totalCoinCount = 0, handPos;
	struct gameState G;

	printf("START RANDOM TEST CASE: ONE SHUFFLE.\n");

	for (n = 1; n <= 20000; n++) {
		for (i = 0; i < sizeof(struct gameState); i++) {
			((char *) &G)[i] = floor(Random() * 256);
		}

		/* Randomly Set Game Numbers */
		G.numPlayers = floor(Random() * (MAX_PLAYER - 1)) + 2;
		G.whoseTurn = floor(Random() * G.numPlayers);
		G.deckCount[G.whoseTurn] = floor(Random() * SAMPLE_SIZE);
		G.discardCount[G.whoseTurn] = floor(Random() * SAMPLE_SIZE);
		G.handCount[G.whoseTurn] = floor(Random() * SAMPLE_SIZE) + 1;
		G.playedCardCount = floor(Random() * SAMPLE_SIZE);
		G.coins = 0;
		totalCoinCount = 0;

		/* Set deck up where at least one card is in the deck */
		while (G.deckCount[G.whoseTurn] < 1) {
			G.deckCount[G.whoseTurn] = floor(Random() * SAMPLE_SIZE);
		}

		/* Set deck up where at least one card is in the discard */
		while (G.discardCount[G.whoseTurn] < 1) {
			G.discardCount[G.whoseTurn] = floor(Random() * SAMPLE_SIZE);
		}


		/* Make 1 cards in deck be coin */
		deckPos = floor(Random() * G.deckCount[G.whoseTurn]);
		G.deck[G.whoseTurn][deckPos] = floor(Random() * (gold - copper + 1)) + copper;
		
		/* Make 1 card in discard be coin */
		discardPos = floor(Random() * G.discardCount[G.whoseTurn]);
		G.discard[G.whoseTurn][discardPos] = floor(Random() * (gold - copper + 1)) + copper;

		/* Add 2 coins to totalCoinCount */
		totalCoinCount += 2;

		/* Set 1 random card in hand as adventurer */
		handPos = floor(Random() * G.handCount[G.whoseTurn]);

		/* Fill rest of the deck with cards */
		for (i = 0; i < G.deckCount[G.whoseTurn]; i++) {
			if (i != deckPos) {
				G.deck[G.whoseTurn][i] = floor(Random() * treasure_map);
				while (G.deck[G.whoseTurn][i] == copper || G.deck[G.whoseTurn][i] == silver || G.deck[G.whoseTurn][i] == gold) {
					G.deck[G.whoseTurn][i] = floor(Random() * treasure_map);
				}
			}
		}

		/* Fill rest of the discard with cards */
		for (i = 0; i < G.discardCount[G.whoseTurn]; i++) {
			if (i != discardPos) {
				G.discard[G.whoseTurn][i] = floor(Random() * treasure_map);
				while (G.discard[G.whoseTurn][i] == copper || G.discard[G.whoseTurn][i] == silver || G.discard[G.whoseTurn][i] == gold) {
					G.discard[G.whoseTurn][i] = floor(Random() * treasure_map);
				}
			}
		}

		/* Fill hand with cards */
		for (i = 0; i < G.handCount[G.whoseTurn]; i++) {
			
			if (i == handPos) {
				G.hand[G.whoseTurn][i] = adventurer;
			} else {	
				G.hand[G.whoseTurn][i] = floor(Random() * treasure_map);
				if (G.hand[G.whoseTurn][i] == copper || G.hand[G.whoseTurn][i] == silver || G.hand[G.whoseTurn][i] == gold) {
					totalCoinCount++;
				}
			}

			/* Set up coins based on cards in hand */
			if (G.hand[G.whoseTurn][i] == copper) {
				G.coins += 1;
			} else if (G.hand[G.whoseTurn][i] == silver) {
				G.coins += 2;
			} else if (G.hand[G.whoseTurn][i] == gold) {
				G.coins += 3;
			}
		}

		if (verboseFlag) {
			printf("Test #%5d: plyr=%d dck=%3d hnd=%3d dsc=%3d hndPos=%3d pos1=%3d pos2=%3d\n", 
					n, G.whoseTurn, G.deckCount[G.whoseTurn], G.handCount[G.whoseTurn], G.discardCount[G.whoseTurn], handPos, deckPos, discardPos);
		}
		
		checkAdventurer(&G, handPos, deckPos, discardPos, 2, 1, totalCoinCount, verboseFlag);
	}
	return 0;
}

int twoShuffleCase(int verboseFlag)
{
	int i, n, pos1, pos2, totalCoinCount = 0, handPos;
	struct gameState G;

	printf("START RANDOM TEST CASE: TWO SHUFFLE.\n");

	for (n = 1; n <= 20000; n++) {
		for (i = 0; i < sizeof(struct gameState); i++) {
			((char *) &G)[i] = floor(Random() * 256);
		}

		/* Randomly Set Game Numbers */
		G.numPlayers = floor(Random() * (MAX_PLAYER - 1)) + 2;
		G.whoseTurn = floor(Random() * G.numPlayers);
		G.deckCount[G.whoseTurn] = floor(Random() * SAMPLE_SIZE);
		G.discardCount[G.whoseTurn] = floor(Random() * SAMPLE_SIZE);
		G.handCount[G.whoseTurn] = floor(Random() * SAMPLE_SIZE) + 1;
		G.playedCardCount = floor(Random() * SAMPLE_SIZE);
		G.coins = 0;
		totalCoinCount = 0;

		/* Set deck up where at least one card is in the deck */
		while (G.deckCount[G.whoseTurn] < 1) {
			G.deckCount[G.whoseTurn] = floor(Random() * SAMPLE_SIZE);
		}

		/* Set deck up where at least two card is in the discard */
		while (G.discardCount[G.whoseTurn] < 2) {
			G.discardCount[G.whoseTurn] = floor(Random() * SAMPLE_SIZE);
		}

		/* Make 2 cards in discard be coins */
		pos1 = floor(Random() * G.discardCount[G.whoseTurn]);
		G.discard[G.whoseTurn][pos1] = floor(Random() * (gold - copper + 1)) + copper;
		pos2 = floor(Random() * G.discardCount[G.whoseTurn]);
		while (pos2 == pos1) {
			pos2 = floor(Random() * G.discardCount[G.whoseTurn]);
		}
		G.discard[G.whoseTurn][pos2] = floor(Random() * (gold - copper + 1)) + copper;

		/* Add 2 coins to totalCoinCount */
		totalCoinCount += 2;

		/* Set 1 random card in hand as adventurer */
		handPos = floor(Random() * G.handCount[G.whoseTurn]);

		/* Fill rest of the deck with cards */
		for (i = 0; i < G.deckCount[G.whoseTurn]; i++) {
			G.deck[G.whoseTurn][i] = floor(Random() * treasure_map);
			while (G.deck[G.whoseTurn][i] == copper || G.deck[G.whoseTurn][i] == silver || G.deck[G.whoseTurn][i] == gold) {
				G.deck[G.whoseTurn][i] = floor(Random() * treasure_map);
			}
		}

		/* Fill rest of the discard with cards */
		for (i = 0; i < G.discardCount[G.whoseTurn]; i++) {
			if (i != pos1 && i != pos2) {
				G.discard[G.whoseTurn][i] = floor(Random() * treasure_map);
				while (G.discard[G.whoseTurn][i] == copper || G.discard[G.whoseTurn][i] == silver || G.discard[G.whoseTurn][i] == gold) {
					G.discard[G.whoseTurn][i] = floor(Random() * treasure_map);
				}
			}
		}

		/* Fill hand with cards */
		for (i = 0; i < G.handCount[G.whoseTurn]; i++) {
			
			if (i == handPos) {
				G.hand[G.whoseTurn][i] = adventurer;
			} else {	
				G.hand[G.whoseTurn][i] = floor(Random() * treasure_map);
				if (G.hand[G.whoseTurn][i] == copper || G.hand[G.whoseTurn][i] == silver || G.hand[G.whoseTurn][i] == gold) {
					totalCoinCount++;
				}
			}

			/* Set up coins based on cards in hand */
			if (G.hand[G.whoseTurn][i] == copper) {
				G.coins += 1;
			} else if (G.hand[G.whoseTurn][i] == silver) {
				G.coins += 2;
			} else if (G.hand[G.whoseTurn][i] == gold) {
				G.coins += 3;
			}
		}

		if (verboseFlag) {
			printf("Test #%5d: plyr=%d dck=%3d hnd=%3d dsc=%3d hndPos=%3d pos1=%3d pos2=%3d\n", 
					n, G.whoseTurn, G.deckCount[G.whoseTurn], G.handCount[G.whoseTurn], G.discardCount[G.whoseTurn], handPos, pos1, pos2);
		}
		
		checkAdventurer(&G, handPos, pos1, pos2, 3, 1, totalCoinCount, verboseFlag);
	}
	return 0;
}

int main(int argc, char **argv)
{
	int verboseFlag = 0;

	signal(SIGABRT, sigabrt_catch);

	printf("Testing cardEffect adventurer.\n");

	/* Set Random Number Generator */
	SelectStream(1);
	PutSeed(2);

	if (argc > 1 && strcmp(argv[1], "-v") == 0) {
		verboseFlag = 1;
	}

	noShuffleCase(verboseFlag);
	oneShuffleCase(verboseFlag);
	twoShuffleCase(verboseFlag);

	printf ("ALL TESTS OK\n");

	return 0;
}